﻿// (c) Copyright 2013 Mike Riedel.
// This source is subject to the Microsoft Public License (Ms-PL).
// All other rights reserved.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace FreshMeat.Threading
{
   /// <summary>
   /// A reader-writer lock implemented using Microsoft's <see cref="ReaderWriterLockSlim"/>.
   /// </summary>
   public class SlimReaderWriterLock : IReaderWriterLock
   {
      readonly ReaderWriterLockSlim slim;
      readonly Reader reader;
      readonly Writer writer;

      class Reader : ILock
      {
         readonly ReaderWriterLockSlim slim;
         readonly LockContext lockContext;

         public Reader(ReaderWriterLockSlim slim)
         {
            this.slim = slim;
            lockContext = new LockContext(Done);
         }

         public void Wait()
         {
            slim.EnterReadLock();
         }

         public Task WaitAsync()
         {
            // If we can get the lock instantly then return an already-completed task.
            if (slim.TryEnterReadLock(0))
               return TaskEx.FromResult<object>(null);

            // Otherwise, return a task that waits for the lock.
            return TaskEx.Run(() => slim.EnterReadLock());
         }

         public void Done()
         {
            slim.ExitReadLock();
         }

         public IDisposable Context()
         {
            Wait();
            return lockContext;
         }
      }

      class Writer : ILock
      {
         readonly ReaderWriterLockSlim slim;
         readonly LockContext context;

         public Writer(ReaderWriterLockSlim slim)
         {
            this.slim = slim;
            context = new LockContext(Done);
         }

         public void Wait()
         {
            slim.EnterWriteLock();
         }

         public Task WaitAsync()
         {
            // If we can get the lock instantly then return an already-completed task.
            if (slim.TryEnterWriteLock(0))
               return TaskEx.FromResult<object>(null);

            // Otherwise, return a task that waits for the lock.
            return TaskEx.Run(() => slim.EnterWriteLock());
         }

         public void Done()
         {
            slim.ExitWriteLock();
         }

         public IDisposable Context()
         {
            Wait();
            return context;
         }
      }

      /// <summary>
      /// Creates instance using specified parameters.
      /// </summary>
      /// <param name="allowRecursion">Whether to allow recursion, which means the same thread can recursively enter the lock.</param>
      public SlimReaderWriterLock(bool allowRecursion = false)
      {
         slim = new ReaderWriterLockSlim(allowRecursion ? LockRecursionPolicy.SupportsRecursion : LockRecursionPolicy.NoRecursion);
         reader = new Reader(slim);
         writer = new Writer(slim);
      }

      /// <summary>
      /// Gets the lock for read access.
      /// </summary>
      /// <returns></returns>
      public ILock Read()
      {
         return reader;
      }

      /// <summary>
      /// Gets the lock for write access.
      /// </summary>
      /// <returns></returns>
      public ILock Write()
      {
         return writer;
      }
   }
}